/*
 * FileSyncHandler.cpp
 *
 * */

#include "FileSyncHandler.h"
#include "ObtenerContenidosArchivo.h"
#include "HexABin.h"

FileSyncHandler::FileSyncHandler(std::string pathRepo, std::fstream& indice,
		std::string pathIndex) : indiceHashes(indice) {
	this->dirPath = pathRepo;
	this->indexPath = pathIndex;
}

FileSyncHandler::~FileSyncHandler() {}

void FileSyncHandler::borrarTodo() {
	std::string filenameIndice, filehashIndice;
	FileDateModification fDateIndice;

	this->indiceHashes.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::ios::in);
	this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
			fDateIndice);
	// Actualizo el indice
	while(filenameIndice.compare("") != 0) {
		// No escribo el archivo del mismo nombre
		std::string aux = dirPath;
		aux += filenameIndice;
		remove(aux.c_str());
		this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
				fDateIndice);
	}
	this->indiceHashes.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::ios::out);
}

void FileSyncHandler::borrarArchivo(const char* filename) {
	std::string indiceAuxPath = this->dirPath;
	indiceAuxPath += "indice/indiceAux";
	std::fstream indiceAux(indiceAuxPath.c_str(), std::ios::out);
	std::string filenameIndice, filehashIndice;
	FileDateModification fDateIndice;

	// Remuevo del repo
	std::string fullPath = this->dirPath;
	fullPath += filename;
	remove(fullPath.c_str());

	this->indiceHashes.close();
	this->indiceHashes.open(this->indexPath.c_str());
	this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
			fDateIndice);
	// Actualizo el indice
	while(filenameIndice.compare("") != 0) {
		// No escribo el archivo del mismo nombre
		if (filenameIndice.compare(filename) != 0) {
			this->escribirArchivoAIndice(indiceAux, filenameIndice, filehashIndice,
					fDateIndice);
		}
		this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
				fDateIndice);
	}

	// Borro indice desactualizado y seteo el nuevo
	this->indiceHashes.close();
	remove(this->indexPath.c_str());
	rename(indiceAuxPath.c_str(), this->indexPath.c_str());
	indiceAux.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::fstream::in);
}

void FileSyncHandler::crearArchivo(const char* filename, const char* d) {

	std::string indiceAuxPath = this->dirPath;
		indiceAuxPath += "indice/indiceAux";
	std::fstream indiceAux(indiceAuxPath.c_str(), std::fstream::out), newFile;
	std::string filenameIndice, filehashIndice, datos = d;
	FileDateModification fDateIndice, fDateNewArch;

	unsigned int tam = datos.size()  / 2;
	uint8_t * data = new uint8_t [tam];
	hexABin(datos, data);


	// Creo archivo en el repo
	std::string fullPath(dirPath);
	fullPath += filename;
	newFile.open(fullPath.c_str(), std::fstream::out);
	newFile.write((char*)data, tam);
	fDateNewArch.createFileModDate(fullPath.c_str());
	newFile.close();

	// Actualizo el indice
	this->indiceHashes.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::ios::in);
	bool escribio = false;
	this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
				fDateIndice);

	while(this->indiceHashes.good()) {
		int comparacion = filenameIndice.compare(filename);

		if (comparacion > 0) {
			// Si el nombre del archivo del indice es mayor, escribo primero el nuevo
			if (!escribio) {
				this->escribirArchivoAIndice(indiceAux, filename, datos, fDateNewArch);
				escribio = true;
			}
			this->escribirArchivoAIndice(indiceAux, filenameIndice, filehashIndice,
					fDateIndice);
		} else if (comparacion == 0) {
			throw MyException("FileSyncHandler) Archivo nuevo es igual a uno ya "
					"indexado en el repo local");
		} else {
			this->escribirArchivoAIndice(indiceAux, filenameIndice, filehashIndice,
					fDateIndice);
		}
		this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
					fDateIndice);
	}

	if (!escribio) {
		this->escribirArchivoAIndice(indiceAux, filename, datos, fDateNewArch);
	}

	// Borro indice desactualizado y seteo el nuevo
	this->indiceHashes.close();
	remove(this->indexPath.c_str());
	rename(indiceAuxPath.c_str(), this->indexPath.c_str());
	indiceAux.close();
	this->indiceHashes.open(this->indexPath.c_str(),
			std::fstream::in | std::fstream::out);

	delete [] data;
}

void FileSyncHandler::actualizarIndice(const std::string& filename) {
	std::string filenameIndice, hashIndice;
	FileDateModification fDate;
	std::string indiceAuxPath = this->dirPath;
	indiceAuxPath += "indice/indiceAux";
	std::fstream indiceAux(indiceAuxPath.c_str(), std::fstream::out);

	this->indiceHashes.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::fstream::in);

	this->getArchivoIndice(this->indiceHashes, filenameIndice, hashIndice, fDate);
	while (filenameIndice.compare("") != 0) {
		if (filenameIndice.compare(filename) == 0) {
			// Consigo el nuevo hash
			std::string fullPath(this->dirPath);
			fullPath += filename;

			std::string buff = obtenerContenidosArchivo(fullPath.c_str());

			hashIndice = buff;
			//delete [] buff;
		}
		this->escribirArchivoAIndice(indiceAux, filenameIndice, hashIndice, fDate);
		this->getArchivoIndice(this->indiceHashes, filenameIndice, hashIndice, fDate);
	}
	// Borro indice desactualizado y renombro el nuevo
	this->indiceHashes.close();
	remove(this->indexPath.c_str());
	rename(indiceAuxPath.c_str(), this->indexPath.c_str());
	indiceAux.close();
	this->indiceHashes.open(this->indexPath.c_str(), std::fstream::in);
}

void FileSyncHandler::mergearRepos(const std::list<FileInfo>& files,
		const std::list<std::string>& oldHashes) {
	// Estructuras auxiliares para leer del indice
	std::string filenameIndice, filehashIndice;
	FileDateModification fDateIndice;

	std::list<std::string>::const_iterator hashIt = oldHashes.begin();
	// Comparo cada archivo de la lista con mi indice actual
	std::list<FileInfo>::const_iterator it=files.begin();
	this->getArchivoIndice(this->indiceHashes, filenameIndice, filehashIndice,
			fDateIndice);
	while ((it != files.end()) && (filenameIndice.compare("") != 0)) {
		int comparacion = filenameIndice.compare(it->getFilename());
		if (comparacion == 0) {
			// Comparo fechas de modificacion
				if (filehashIndice.compare(it->getHash()) != 0) {
					// Actualizar (archivo en repo local desactualizado)
					this->diferencias.push_back(new
							DiferenciaModifiedFile(it->getFilename().c_str(),
									fDateIndice, this->dirPath, *hashIt));
					hashIt++;
				}
			it++;
			this->getArchivoIndice(this->indiceHashes, filenameIndice,
					filehashIndice, fDateIndice);
		} else if (comparacion < 0) {
			// No es archivo nuevo --> Archivo borrado del server --> borro en el repo
			this->diferencias.push_back(new
					DiferenciaGetDeletedFile(filenameIndice.c_str()));
			this->getArchivoIndice(this->indiceHashes, filenameIndice,
					filehashIndice, fDateIndice);
		} else /* comparacion > 0 */ {
			// Archivo nuevo en el server (crear archivo en el repo local)
				this->diferencias.push_back(new
						DiferenciaGetNewFile(it->getFilename().c_str()));
			it++;
		}
	}

	if (it == files.end()) {
		// Borrar los restantes en el repo local
		while (filenameIndice.compare("") != 0) {
				this->diferencias.push_back(new
						DiferenciaGetDeletedFile(filenameIndice.c_str()));
			this->getArchivoIndice(this->indiceHashes, filenameIndice,
					filehashIndice, fDateIndice);
		}
	} else {
		// Archivos nuevos que restan en la lista
		do {
				this->diferencias.push_back(new
						DiferenciaGetNewFile(it->getFilename().c_str()));
			it++;
		} while (it != files.end());
	}
}

//	Estructura del indice: nombreArchivo, hash, fechaModificacion, 1 archivo por linea
void FileSyncHandler::escribirArchivoAIndice(std::fstream& indice, const std::string& nombre,
		const std::string& data, const FileDateModification& fechaMod) {
	indice << nombre;
	indice << " ";
	indice << md5(data);
	indice << " ";
	indice << fechaMod;
	indice << '\n';
}

void FileSyncHandler::getArchivoIndice(std::fstream& indice, std::string& nombre,
		std::string& hash, FileDateModification& fechaMod) {
	nombre.clear();
	if (indice.good()) {
		indice >> nombre;
		indice >> hash;
		indice >> fechaMod;
	}
}

void FileSyncHandler::backUpFile (const std::string& filename) {
	std::string fileFullPath = (this->dirPath + filename);
	std::string backUpPath = this->dirPath;
	backUpPath += "backUp/";
	backUpPath += filename;

	std::fstream backUp(backUpPath.c_str(), std::ios_base::out);

	// Obtengo los contenidos del archivo y lo copio.
	std::string buff = obtenerContenidosArchivo(fileFullPath.c_str());
	backUp.write(buff.c_str(), buff.size());

	backUp.close();
}

std::string FileSyncHandler::getHashArchivo(const std::string& filename) {
	std::string fileFullPath = (this->dirPath + filename), hash;
	std::string buff = obtenerContenidosArchivo(fileFullPath.c_str());
	hash = md5(buff.c_str());
	return (hash);
}
